using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace igp.net.DataModel
{
    /// <summary>
    /// Specifies the method of an integral calculation.
    /// </summary>
    public enum IgpIntegralType
    {
        /// <summary>
        /// Should be in fact pseudo accurate. 
        /// It calculates an acurrate value only for polynomials 
        /// and only for ranges that do not contain monomials' limit ranges.
        /// </summary>
        Acurrate,
        /// <summary>
        /// Numeric - trapezoid method
        /// </summary>
        NumericTrapez
    }

    /// <summary>
    /// Specifies the integral user interaction mode.
    /// </summary>
    public enum IgpIntegralInteraction
    {
        /// <summary>
        /// No interaction is taking place at the moment
        /// </summary>
        None,
        /// <summary>
        /// User can move the lower range limit.
        /// </summary>
        MoveFrom,
        /// <summary>
        /// User can move the upper range limit.
        /// </summary>
        MoveTo,
        /// <summary>
        /// User can move both range limits.
        /// </summary>
        Move
    }

    /// <summary>
    /// Represents an integral.
    /// </summary>
    public class igpIntegral : IgpItem
    {
        private int divisions;
        private double oFrom;

        private double oTo;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public igpIntegral()
        {
            ItemName = "Integral ";
            TypeID = IgpItemType.Integral;
            LineColor = Color.FromArgb(50, 0, 0, 128);

            TopFunctionBase = IgpProject.Singleton.GetFunction(0);
            BottomFunctionBase = null;

            TopFunctionName = "";
            BottomFunctionName = "";

            Type = IgpIntegralType.Acurrate;
            Divisions = 4;

            From = 0;
            To = 2;

            Interact = IgpIntegralInteraction.None;
        }

        /// <summary>
        /// Top function of the integral.
        /// </summary>
        public IgpFunctionBase TopFunctionBase { get; set; }

        /// <summary>
        /// Bottom function of the integral.
        /// If set to null - ox axis will be considered the bottom function.
        /// </summary>
        public IgpFunctionBase BottomFunctionBase { get; set; }

        internal string TopFunctionName { get; set; }

        internal string BottomFunctionName { get; set; }

        /// <summary>
        /// Specifies user interaction mode.
        /// </summary>
        public IgpIntegralInteraction Interact { get; set; }

        /// <summary>
        /// Specifies the integral calculation method.
        /// </summary>
        public IgpIntegralType Type { get; set; }

        /// <summary>
        /// Numver of divisions for trapezoid method.
        /// </summary>
        public int Divisions
        {
            get { return divisions; }
            set
            {
                if (value <= 0) throw new ArgumentException("Divisions must be positive!");
                divisions = value;
            }
        }

        /// <summary>
        /// Gets/Sets lower range limit.
        /// </summary>
        public double From { get; set; }

        /// <summary>
        /// Gets/Sets upper range limit.
        /// </summary>
        public double To { get; set; }

        private double GetTopValue(double x)
        {
            return TopFunctionBase.Value(x);
        }

        private double GetBottomValue(double x)
        {
            if (BottomFunctionBase == null)
            {
                IgpView view = IgpProject.Singleton.View;
                return view.LogScaleY ? view.Axes.YCoordinateLogarythmic : view.Axes.YCoordinateLinear;
            }

            return BottomFunctionBase.Value(x);
        }

        private double GetTopIntegral(double x)
        {
            return TopFunctionBase.Integral(x);
        }

        private double GetBottomIntegral(double x)
        {
            if (BottomFunctionBase == null)
            {
                IgpView view = IgpProject.Singleton.View;
                return view.LogScaleY ? x*view.Axes.YCoordinateLogarythmic : x*view.Axes.YCoordinateLinear;
            }

            return BottomFunctionBase.Integral(x);
        }

        /// <summary>
        /// Draws the integral on the screen.
        /// </summary>
        /// <param name="graph">Graphics object used for drawing</param>
        public override void Draw(Graphics graph)
        {
            IgpView view = IgpProject.Singleton.View;

            if (Type == IgpIntegralType.Acurrate)
            {
                int width = view.GetScreenCoordinateX(To) - view.GetScreenCoordinateX(From);

                var div = (int) (0.5*width);

                double h2 = (To - From)/div;
                int px = 0;
                int py1 = 0;
                int py2 = 0;

                for (int l = 0; l <= div; l++)
                {
                    double x = From + l*h2;
                    double ty1 = GetTopValue(x);
                    double ty2 = GetBottomValue(x);

                    int ax = view.GetScreenCoordinateX(x);
                    int y1 = view.GetScreenCoordinateY(ty1);
                    int y2 = view.GetScreenCoordinateY(ty2);


                    if (l != 0)
                    {
                        Point[] points = {
                                             new Point(px, py1),
                                             new Point(ax, y1),
                                             new Point(ax, y2),
                                             new Point(px, py2)
                                         };

                        graph.FillPolygon(new SolidBrush(LineColor), points);
                    }

                    py1 = y1;
                    py2 = y2;
                    px = ax;
                }
            }
            else if (Type == IgpIntegralType.NumericTrapez)
            {
                int width = view.GetScreenCoordinateX(To) - view.GetScreenCoordinateX(From);
                int div = Divisions;

                if (div > 0.1*width)
                {
                    div = (int) (0.1*width);
                }

                double h2 = (To - From)/div;
                int px = 0;
                int py1 = 0, py2 = 0;

                for (int l = 0; l <= div; l++)
                {
                    double x = From + l*h2;
                    double ty1 = GetTopValue(x);
                    double ty2 = GetBottomValue(x);

                    int ax = view.GetScreenCoordinateX(x);
                    int y1 = view.GetScreenCoordinateY(ty1);
                    int y2 = view.GetScreenCoordinateY(ty2);


                    if (l != 0)
                    {
                        Point[] points = {
                                             new Point(px, py1),
                                             new Point(ax, y1),
                                             new Point(ax, y2),
                                             new Point(px, py2)
                                         };

                        graph.FillPolygon(new SolidBrush(LineColor), points);

                        graph.DrawLine(new Pen(Color.Black), px, py1, ax, y1);
                        graph.DrawLine(new Pen(Color.Black), px, py2, ax, y2);
                    }

                    py1 = y1;
                    py2 = y2;
                    px = ax;
                }
            }
        }

        /// <summary>
        /// This method is required for item's reaction to mouse buttons being pressed. 
        /// You must invoke this base method in your derived class in order 
        /// to update some useful properties.
        /// </summary>
        /// <param name="graph">Graphics object that can be used to paint something on the screen</param>
        /// <param name="e">Mouse Event Details</param>
        public override void InteractDown(Graphics graph, MouseEventArgs e)
        {
            base.InteractDown(graph, e);
            oFrom = From;
            oTo = To;

            if (e.Button != MouseButtons.Left) return;

            IgpView view = IgpProject.Singleton.View;

            if (e.X > view.GetScreenCoordinateX(From) - 10 &&
                e.X < view.GetScreenCoordinateX(From) + 10)
            {
                InteractionInUse = true;
                Interact = IgpIntegralInteraction.MoveFrom;
            }

            if (e.X > view.GetScreenCoordinateX(To) - 10 &&
                e.X < view.GetScreenCoordinateX(To) + 10)
            {
                InteractionInUse = true;
                Interact = IgpIntegralInteraction.MoveTo;
            }

            if (e.X <= view.GetScreenCoordinateX(From) + 10 || e.X >= view.GetScreenCoordinateX(To) - 10) return;
            
            InteractionInUse = true;
            Interact = IgpIntegralInteraction.Move;
        }

        /// <summary>
        /// This method is required for item's reaction to mouse movement. 
        /// You must invoke this base method in your derived class in order 
        /// to update some useful properties.
        /// </summary>
        /// <param name="graph">Graphics object that can be used to paint something on the screen</param>
        /// <param name="e">Mouse Event Details</param>
        public override void InteractMove(Graphics graph, MouseEventArgs e)
        {
            base.InteractMove(graph, e);

            if (InteractionInUse && Interact == IgpIntegralInteraction.None) return;

            IgpView view = IgpProject.Singleton.View;

            if (!InteractionInUse)
            {
                if (e.X > view.GetScreenCoordinateX(From) - 10 && e.X < view.GetScreenCoordinateX(From) + 10)
                {
                    graph.DrawLine(new Pen(Color.Red, 2),
                                   view.GetScreenCoordinateX(From),
                                   view.GetScreenCoordinateY(GetTopValue(From)),
                                   view.GetScreenCoordinateX(From),
                                   view.GetScreenCoordinateY(GetBottomValue(From)));
                }

                if (e.X > view.GetScreenCoordinateX(To) - 10 && e.X < view.GetScreenCoordinateX(To) + 10)
                {
                    graph.DrawLine(new Pen(Color.Red, 2),
                                   view.GetScreenCoordinateX(To),
                                   view.GetScreenCoordinateY(GetTopValue(To)),
                                   view.GetScreenCoordinateX(To),
                                   view.GetScreenCoordinateY(GetBottomValue(To)));
                }

                if (e.X > view.GetScreenCoordinateX(From) + 10 && e.X < view.GetScreenCoordinateX(To) - 10)
                {
                    graph.DrawLine(new Pen(Color.Red, 2),
                                   view.GetScreenCoordinateX(To),
                                   view.GetScreenCoordinateY(GetTopValue(To)),
                                   view.GetScreenCoordinateX(To),
                                   view.GetScreenCoordinateY(GetBottomValue(To)));

                    graph.DrawLine(new Pen(Color.Red, 2),
                                   view.GetScreenCoordinateX(From),
                                   view.GetScreenCoordinateY(GetTopValue(From)),
                                   view.GetScreenCoordinateX(From),
                                   view.GetScreenCoordinateY(GetBottomValue(From)));
                }
            }

            if (Interact != IgpIntegralInteraction.None)
            {
                double max, min;

                if (BottomFunctionBase == null)
                {
                    min = TopFunctionBase.DomainLower;
                    max = TopFunctionBase.DomainHigher;
                }
                else if (BottomFunctionBase.DomainLower == BottomFunctionBase.DomainHigher)
                {
                    min = TopFunctionBase.DomainLower;
                    max = TopFunctionBase.DomainHigher;
                }
                else if (TopFunctionBase.DomainLower == TopFunctionBase.DomainHigher)
                {
                    min = BottomFunctionBase.DomainLower;
                    max = BottomFunctionBase.DomainHigher;
                }
                else
                {
                    min = Math.Max((sbyte) TopFunctionBase.DomainLower, (sbyte) BottomFunctionBase.DomainLower);
                    max = Math.Min((sbyte) TopFunctionBase.DomainHigher, (sbyte) BottomFunctionBase.DomainHigher);
                }

                double DoubleVectorX;
                
                if (!view.LogScaleX)
                {
                    DoubleVectorX = VectorX/view.VDistanceX;
                }
                else
                {
                    DoubleVectorX = VectorX/view.VLogDistanceX;
                }

                double margin = view.GetRealCoordinateX(30) -
                                view.GetRealCoordinateX(10);

                if (To < max && (Interact == IgpIntegralInteraction.MoveFrom || Interact == IgpIntegralInteraction.Move))
                {
                    if (!view.LogScaleX)
                    {
                        From = oFrom - DoubleVectorX;
                    }
                    else
                    {
                        From = Math.Pow(10, (Math.Log10(oFrom) - DoubleVectorX/10));
                    }

                    if (To < (From + margin)) From = To - margin;

                    From = Math.Max(From, min);
                    From = Math.Min(From, max);
                }

                if (From > min && (Interact == IgpIntegralInteraction.MoveTo || Interact == IgpIntegralInteraction.Move))
                {
                    if (!view.LogScaleX)
                    {
                        To = oTo - DoubleVectorX;
                    }
                    else
                    {
                        To = Math.Pow(10, (Math.Log10(oTo) - DoubleVectorX/10));
                    }

                    if (To < (From + margin)) To = From + margin;

                    To = Math.Min(To, max);
                    To = Math.Max(To, min);
                }

                FormMain.Singleton.EditorWindow.ReadIntegralValues(this);
                FormMain.Singleton.EditorWindow.ReadIntegralRanges(this);
            }
        }

        /// <summary>
        /// This method is required for item's reaction to mouse buttons being released. 
        /// </summary>
        /// <param name="graph">Graphics object that can be used to paint something on the screen</param>
        /// <param name="e">Mouse Event Details</param>
        public override void InteractUp(Graphics graph, MouseEventArgs e)
        {
            base.InteractUp(graph, e);
            InteractionInUse = false;
            Interact = IgpIntegralInteraction.None;
        }

        /// <summary>
        /// Loads data from a TextReader.
        /// </summary>
        /// <param name="sr">data input</param>
        public override void ReadData(TextReader sr)
        {
            String Line;
            while ((Line = sr.ReadLine()) != "</integral>")
            {
                char[] sep = {'=', '|'};
                String[] cmd = Line.Split(sep, 3);

                if (cmd[0].StartsWith("ItemName"))
                {
                    ItemName = cmd[1].Substring(1, cmd[1].Length - 1);
                }

                if (cmd[0].StartsWith("From"))
                {
                    From = double.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("To"))
                {
                    To = double.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("FunctionTop"))
                {
                    TopFunctionName = cmd[1].Substring(1, cmd[1].Length - 1);
                }

                if (cmd[0].StartsWith("FunctionBottom"))
                {
                    BottomFunctionName = cmd[1].Substring(1, cmd[1].Length - 1);
                }

                if (cmd[0].StartsWith("Color"))
                {
                    LineColor = Color.FromArgb(int.Parse(cmd[1]));
                }

                if (cmd[0].StartsWith("Divisions"))
                {
                    Divisions = int.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("Type"))
                {
                    Type = (IgpIntegralType) int.Parse(cmd[1]);
                }

                if (cmd[0].StartsWith("Visible"))
                {
                    IsVisible = bool.Parse(cmd[1]);
                }
            }
        }

        /// <summary>
        /// Saves data to a TextWriter
        /// </summary>
        /// <param name="sw">data output</param>
        public override void SaveData(TextWriter sw)
        {
            sw.WriteLine("<integral>");
            sw.WriteLine("ItemName = {0}", ItemName);
            sw.WriteLine("From = {0}", From);
            sw.WriteLine("To = {0}", To);
            sw.WriteLine("FunctionTop = {0}", TopFunctionBase.ItemName);
            if (BottomFunctionBase != null)
            {
                sw.WriteLine("FunctionBottom = {0}", BottomFunctionBase.ItemName);
            }
            sw.WriteLine("Color = {0}", LineColor.ToArgb());
            sw.WriteLine("Divisions = {0}", Divisions);
            sw.WriteLine("Type = {0}", (int) Type);
            sw.WriteLine("IsVisible = {0}", IsVisible);
            sw.WriteLine("</integral>");
        }

        /// <summary>
        /// Calculates value of the integral.
        /// </summary>
        /// <returns>integral value</returns>
        public double Calculate()
        {
            if (Type == IgpIntegralType.NumericTrapez)
            {
                double calka = 0;

                double h2 = (To - From)/Divisions;
                double py1 = 0, py2 = 0;

                for (int l = 0; l <= Divisions; l++)
                {
                    double x = From + l*h2;
                    double ty1 = GetTopValue(x);
                    double ty2 = GetBottomValue(x);

                    if (l != 0)
                    {
                        calka += ((ty1 - ty2) + (py1 - py2))*h2/2;
                    }

                    py1 = ty1;
                    py2 = ty2;
                }

                return calka;
            }

            if (Type == IgpIntegralType.Acurrate)
            {
                return GetTopIntegral(To) - GetTopIntegral(From) - GetBottomIntegral(To) + GetBottomIntegral(From);
            }

            return 0;
        }

        /// <summary>
        /// Calculates length of the top function's curve.
        /// </summary>
        /// <returns>curve length</returns>
        public double TopLength()
        {
            if (Type == IgpIntegralType.NumericTrapez)
            {
                double calka = 0;

                double h2 = (To - From)/Divisions;
                double px = 0, py1 = 0;

                for (int l = 0; l <= Divisions; l++)
                {
                    double x = From + l*h2;
                    double ty1 = GetTopValue(x);

                    if (l != 0)
                    {
                        calka += Math.Sqrt(Math.Pow(x - px, 2) + Math.Pow(ty1 - py1, 2));
                    }

                    px = x;
                    py1 = ty1;
                }

                return calka;
            }

            return -1;
        }

        /// <summary>
        /// Calculates length of the bottom function's curve.
        /// </summary>
        /// <returns>curve length</returns>
        public double BottomLength()
        {
            if (Type == IgpIntegralType.NumericTrapez)
            {
                double calka = 0;

                double h2 = (To - From)/Divisions;
                double px = 0, py1 = 0;

                for (int l = 0; l <= Divisions; l++)
                {
                    double x = From + l*h2;
                    double ty1 = GetBottomValue(x);

                    if (l != 0)
                    {
                        calka += Math.Sqrt(Math.Pow(x - px, 2) + Math.Pow(ty1 - py1, 2));
                    }

                    px = x;
                    py1 = ty1;
                }

                return calka;
            }

            return -1;
        }
    }
}